#ifndef GRAPHICSWIDGET_HEADER_VIZMF_PROJECT
#define GRAPHICSWIDGET_HEADER_VIZMF_PROJECT

#include <QGLWidget>
#include <QTime>
#include <QMouseEvent>
#include <QDebug>
#include <QKeyEvent>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cmath>
#include <ctime>

#include "src/core/generic/vector3.h"
#include "src/core/generic/matrix4.h"
#include "src/core/scene/scene.h"
#include "src/gui/itemwidget.h"

class MaterialGL : public Material
{
// special class for defining MaterialGL properties
public:
  MaterialGL() {setAmbient(); setDiffuse(); setSpecular(); setShininess();}
  MaterialGL(const Material& col)
  {
    setAmbient(col.ambient());
    setDiffuse(col.diffuse());
    setSpecular(col.specular());
    setShininess(col.shininess());
  }
  MaterialGL(const float a, const float b, const float c, const float d)
  {
    setAmbient(a, b, c, d);
    setDiffuse(a, b, c, d);
    setSpecular();
    setShininess();
  }
  void install() const
  {
    glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_);
    glMaterialfv(GL_FRONT, GL_SPECULAR, specular_);
    glMaterialfv(GL_FRONT, GL_SHININESS, shininess_);
  }
};

// Next 2 functions were copied from openGL Programming guide 1.1
inline void accFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_, GLdouble far_, GLdouble pixdx, GLdouble pixdy, GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
    GLdouble xwsize, ywsize;
    GLdouble dx, dy;
    GLint viewport[4];

    glGetIntegerv (GL_VIEWPORT, viewport);

    xwsize = right - left;
    ywsize = top - bottom;
    dx = -(pixdx * xwsize / viewport[2] + eyedx * near_ / focus);
    dy = -( pixdy * ywsize / viewport[3] + eyedy * near_ / focus);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum (left + dx, right + dx, bottom + dy, top + dy,  near_, far_);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef (-eyedx, -eyedy, 0.0);
}

inline void accPerspective(GLdouble fovy, GLdouble aspect,
    GLdouble near_, GLdouble far_, GLdouble pixdx, GLdouble pixdy,
    GLdouble eyedx, GLdouble eyedy, GLdouble focus)
{
    GLdouble fov2,left,right,bottom,top;
    fov2 = ((fovy*M_PI) / 180.0) / 2.0;

    top = near_ / (cos(fov2) / sin(fov2));
    bottom = -top;
    right = top * aspect;
    left = -right;

    accFrustum (left, right, bottom, top, near_, far_,
        pixdx, pixdy, eyedx, eyedy, focus);
}


class GraphicsWidget : public QGLWidget, public ItemWidget
{
Q_OBJECT
public:
  enum ViewMode {MoleculeCentered, FreeLook};
  explicit GraphicsWidget(QWidget *parent = 0);
  ~GraphicsWidget()
  {
    makeCurrent();
    gluDeleteQuadric(quadricObject);
    glDeleteLists(overallLitList, 1);
    glDeleteLists(overallLitListFast, 1);
    glDeleteLists(overallUnlitList, 1);
    glDeleteLists(overallUnlitListFast, 1);
  }

  void setPerspective(const float v) { perspective_ = v; }
  void setDistances(const float a, const float b) { distances_[0] = a, distances_[1] = b; }
  void setSpeed(const float s) {spd = s;}
  void setAntialising(const int level) {antialising = level;}
  void initializeGL()
  {
    glClearColor (0.0, 0.0, 0.0, 0.0);
    glShadeModel (GL_SMOOTH);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_BLEND);
    glEnable(GL_NORMALIZE);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    compNavCube();
  }
  void resizeGL(int width, int height)
  {
    viewport_[0] = width;
    viewport_[1] = height;
    glViewport (0, 0, (GLsizei) width, (GLsizei) height);
  }
  void update() { updateGL(); }
  void paintGL();
  void mousePressEvent(QMouseEvent *event)
  {
    lastMousePos = event->pos();
    QGLWidget::mousePressEvent(event);
  }

  void mouseMoveEvent(QMouseEvent *event);
  void keyPressEvent(QKeyEvent *event);
  void keyReleaseEvent(QKeyEvent *event);
  void wheelEvent(QWheelEvent *event)
  {
    // zooming in and out
    if (viewMode == MoleculeCentered)
    {
      if (event->orientation() == Qt::Vertical)
      {
        // for explanation of magic number 120 look in Qt's manual in QWheelEvent
        cameraCoord += ( ( lookingAt - cameraCoord ) * 0.1 / 120.0 * event->delta());
      }
    }
    QGLWidget::wheelEvent(event);
    updateGL();
  }
  void setPolygonMode(ItemWidget::PolygonMode mode = Fill)
  {
    switch (mode)
    {
    case ItemWidget::Fill:
      gluQuadricDrawStyle(quadricObject, GLU_FILL);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      break;
    case ItemWidget::Points:
      gluQuadricDrawStyle(quadricObject, GLU_POINT);
      glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
      break;
    case ItemWidget::Lines:
      gluQuadricDrawStyle(quadricObject, GLU_LINE);
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      break;
    }
  }
  void setLineWidth(const float width = 1.0)
  {
    glLineWidth(width);
  }
  void setPointSize(const float size = 1.0)
  {
    glPointSize(size);
  }

  void startList(ItemWidget::Lists l)
  {
    switch (l)
    {
    case ItemWidget::Lit:
      glDeleteLists(overallLitList, 1);
      glNewList(overallLitList, GL_COMPILE);
      break;
    case ItemWidget::LitFast:
      glDeleteLists(overallLitListFast, 1);
      glNewList(overallLitListFast, GL_COMPILE);
      break;
    case ItemWidget::Unlit:
      glDeleteLists(overallUnlitList, 1);
      glNewList(overallUnlitList, GL_COMPILE);
      break;
    case ItemWidget::UnlitFast:
      glDeleteLists(overallUnlitListFast, 1);
      glNewList(overallUnlitListFast, GL_COMPILE);
      break;
    }
  }
  void endList()
  {
    glEndList();
  }

  virtual void drawSphere(const Vector3& center, const float radius, const Material& mat, const int detail = 5);
  virtual void drawCylinder(const Vector3& from, const Vector3& to, const float radiusFrom, const float radiusTo, const Material& mat, const int detail = 5 );
  virtual void drawCone(const Vector3& from, const Vector3& to, const float radius, const Material& mat, const int detail = 5);
  virtual void drawParallelepiped(const Vector3& vertex, const Vector3& a, const Vector3& b, const Vector3& c, const Material& mat );

  // other primitives go here

  void addLight(const Light& light)
  {
    lights.push_back(light);
  }

private:
  Vector3 calcCameraDirection()
  { // calculates camera coords from angles
    return - Vector3(sin(cameraFi) * sin(cameraTheta), cos(cameraFi) * sin(cameraTheta), cos(cameraTheta) ) ;
  }
  void compAngles(const Vector3& v) // calculates camera angles
  {
    lookingAt = v;
    Vector3 lookingDir = cameraCoord - lookingAt;
    Vector3 xyProj = Vector3(lookingDir[0], lookingDir[1], 0.0).normalize();
    if (lookingDir[0] > 0)
      cameraFi = M_PI - acos( xyProj.dot(Vector3(0.0, 1.0, 0.0)));
    else
      cameraFi = M_PI + acos( xyProj.dot(Vector3(0.0, 1.0, 0.0)));
    cameraTheta = - acos(lookingDir.dot(Vector3(0.0, 0.0, 1.0)) / lookingDir.length());
  }
  void orientCamera()
  {
    glMatrixMode(GL_MODELVIEW);
    glRotatef(cameraPsy / M_PI * 180.0, 0.0, 0.0, 1.0);
    glRotatef(cameraTheta / M_PI * 180.0, 1.0, 0.0, 0.0);
    glRotatef(cameraFi / M_PI * 180.0, 0.0, 0.0, 1.0);
    glTranslated(-cameraCoord[0], -cameraCoord[1], -cameraCoord[2]);
  }

  void installLights()
  {
    glMatrixMode(GL_MODELVIEW);
    // first installing camera light
    glPushMatrix();
    glLoadIdentity();
    const GLfloat a[] = {-1.0, 0.0, 1.0, 1.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, a);
    glPopMatrix();
    // next installing other lights
    for (std::vector<Light>::const_iterator i = lights.begin(), e = lights.end(); i!=e; ++i)
    {
      glPushMatrix();
      const GLfloat a[] = {i->position()[0], i->position()[1], i->position()[2], 1.0 };
      glLightfv(GL_LIGHT0, GL_POSITION, a);
      glPopMatrix();
    }
  }
  void compNavCube();
  void drawNavCube();
signals:

public slots:
private:
  int viewport_[2];
  float distances_[2];
  GLfloat perspective_;
  GLuint overallLitList, overallLitListFast, overallUnlitList, overallUnlitListFast, navCube;
  Vector3 cameraCoord, lookingAt;
  float cameraFi, cameraTheta, cameraPsy;
  QPoint lastMousePos;
  clock_t moveTimer, fpsTimer;
  float spd;
  ViewMode viewMode;
  bool isMoving, lowPerfomance;
  GLUquadricObj *quadricObject;
  int antialising;
  std::vector<Light> lights;
};

#endif // GRAPHICSWIDGET_HEADER_VIZMF_PROJECT
